/*
    Copyright 2005-2007 Adobe Systems Incorporated

    Use, modification and distribution are subject to the Boost Software License,
    Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
    http://www.boost.org/LICENSE_1_0.txt).
*/

#ifndef XPCOM_DETAIL_MOVE_HPP
#define XPCOM_DETAIL_MOVE_HPP

#include <xpcom/config.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/or.hpp>
#include <boost/type_traits/is_class.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/utility/enable_if.hpp>

namespace xpcom {

namespace detail {

template <typename T>
struct class_has_move_assign {
    class type {
        typedef T& (T::*E)(T t);
        typedef char (&no_type)[1];
        typedef char (&yes_type)[2];
        template <E e> struct sfinae { typedef yes_type type; };
        template <class U>
        static typename sfinae<&U::operator=>::type test(int);
        template <class U>
        static no_type test(...);
    public:
        enum {value = sizeof(test<T>(1)) == sizeof(yes_type)};
    };
 };

template<typename T>
struct has_move_assign : boost::mpl::and_<boost::is_class<T>, class_has_move_assign<T> > {};

class test_can_convert_anything { };

/*
    REVISIT (sparent@adobe.com): This is a work around for Boost 1.34.1 and VC++ 2008 where
    boost::is_convertible<T, T> fails to compile.
*/
template <typename T, typename U>
struct is_convertible : boost::mpl::or_<
    boost::is_same<T, U>,
    boost::is_convertible<T, U>
> { };

} //namespace xpcom::detail


template <typename T>
struct move_from
{
    explicit move_from(T& x) : source(x) { }
    T& source;
};

template <typename T>
struct is_movable : boost::mpl::and_<
                        boost::is_convertible<move_from<T>, T>,
                        detail::has_move_assign<T>,
                        boost::mpl::not_<boost::is_convertible<detail::test_can_convert_anything, T> >
                    > { };


#if !defined(BOOST_NO_SFINAE)

template <typename T,
          typename U = T,
          typename R = void*>
struct copy_sink : boost::enable_if<
                        boost::mpl::and_<
                            xpcom::detail::is_convertible<T, U>,
                            boost::mpl::not_<is_movable<T> >
                        >,
                        R
                    >
{ };

template <typename T,
          typename U = T,
          typename R = void*>
struct move_sink : boost::enable_if<
                        boost::mpl::and_<
                            xpcom::detail::is_convertible<T, U>,
                            is_movable<T>
                        >,
                        R
                    >
{ };

template <typename T>
T move(T& x, typename move_sink<T>::type = 0) { return T(move_from<T>(x)); }

template <typename T>
T& move(T& x, typename copy_sink<T>::type = 0) { return x; }

#else // BOOST_NO_SFINAE

// On compilers without SFINAE, define copy_sink to always use the copy function.
template <typename T,
          typename U = T,
          typename R = void*>
struct copy_sink
{
    typedef R type;
};

// Always copy the element unless this is overloaded.

template <typename T>
T& move(T& x) {
    return x;
}

#endif // BOOST_NO_SFINAE

} // namespace xpcom

#endif // XPCOM_DETAIL_MOVE_HPP
